package syntaxcolor.v4.syntaxcolor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ExtendedModifyEvent;
import org.eclipse.swt.custom.ExtendedModifyListener;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;

import syntaxcolor.v4.syntaxcolor.rule.IRule;
import syntaxcolor.v4.syntaxcolor.scanner.Scanner;
import syntaxcolor.v4.syntaxcolor.token.Token;

public class CodeStyleText extends StyledText {
	public static final String DEFAULT = "default";
	private static final int UNDO = SWT.CTRL + 'Z'; // 撤消
	private static final int REDO = SWT.CTRL + SWT.SHIFT + 'Z';// 重做

	Map<String, StyleRange> styles = new HashMap<String, StyleRange>();
	private List<Token> tokenList = new ArrayList<Token>();
	private List<IRule> rules = new ArrayList<IRule>();
	private Scanner scanner = null;
	private ExtendedModifyEvent undos[] = new ExtendedModifyEvent[20]; // 撤消
	private boolean undoLock = false; // 锁

	public CodeStyleText(Composite parent, int style) {
		super(parent, style);

		scanner = new Scanner(getContent());
		addExtendedModifyListener(new ExtendedModifyListener() {
			public void modifyText(ExtendedModifyEvent event) {
				if (event.replacedText.length() > 0) {
					processPaint(event.start, -event.replacedText.length());
				}
				if (event.length > 0)
					processPaint(event.start, event.length);
				if (!undoLock) {
					ExtendedModifyEvent[] newhistory = new ExtendedModifyEvent[undos.length];
					System.arraycopy(undos, 0, newhistory, 1, undos.length - 1);
					newhistory[0] = event;
					undos = newhistory;
				}
			}
		});
		setKeyBinding('Z' | SWT.CTRL, UNDO);
		setKeyBinding('Z' | SWT.CTRL | SWT.SHIFT, REDO);
		styles.put(DEFAULT, new StyleRange());

	}

	@Override
	public void setText(String text) {
		try {
			setUndoLock(true);
			super.setText(text);
			undos = new ExtendedModifyEvent[undos.length];// 清空历史记录
		} finally {
			setUndoLock(false);
		}
	}

	@Override
	public void invokeAction(int action) {
		super.invokeAction(action);
		if (action == UNDO) {
			if (undos[0] != null) {
				try {
					setUndoLock(true);
					replaceTextRange(undos[0].start, undos[0].length,
							undos[0].replacedText);
					setCaretOffset(undos[0].start);
					ExtendedModifyEvent[] newhistory = new ExtendedModifyEvent[undos.length];
					System.arraycopy(undos, 1, newhistory, 0, undos.length - 1);
					undos = newhistory;
				} finally {
					setUndoLock(false);
				}
			}
		} else if (action == REDO) {

		}
	}

	private void processPaint(final int startOffset, final int shift) {
		Display.getDefault().syncExec(new Runnable() {
			public void run() {
				Token firstAffectToken = null;
				int index = 0;
				// 开始计算对现有的文字分段造成的影响
				for (Token t : tokenList) {
					// 找到中间是修改起点的文字分段，做为要处理的第一个分段
					if (t.getOffset() < startOffset
							&& t.getOffset() + t.getLength() > startOffset) {
						firstAffectToken = t;
						break;
					}
					// 如果正好从一个分段的开始修改，则将此分段的前一个分段做为第一个分段（因为前一个分段可能与修改后的文字重新
					// 组合，变成新的分段
					if (t.getOffset() == startOffset && index > 0) {
						firstAffectToken = tokenList.get(--index);
						break;
					}
					index++;
				}
				// 如果第一个分段是空，可能没有分段，则重新创建分段
				if (firstAffectToken == null) {
					index = 0;
					firstAffectToken = new Token(0, 0, DEFAULT);
				}
				final Token fromToken = firstAffectToken;
				final int fromIndex = index;
				// 从被影响的第一个分段开始着色，同进还要调整后续未修改分段的起点shift
				setSyntaxColor(fromToken, fromIndex, shift);
				// 处理完成后，就会生成当前文字的着色分段列表，可以被重用，如生成HTML
			}
		});
	}

	protected void setSyntaxColor(Token startToken, int affectIndex, int shift) {
		// 首先认为开始的文字是不需着色的默认样式
		Token defaultToken = new Token(startToken.getOffset(), 0, DEFAULT);
		// 指定开始扫描的文字偏移量
		scanner.setOffset(defaultToken.getOffset());
		// 新的着色分段列表
		List<Token> newList = new ArrayList<Token>();
		// 首先将未受影响的分段（也就是修改发生之前的分段）复制过来
		for (int i = 0; i < affectIndex; ++i) {
			newList.add(tokenList.get(i));
		}
		Token newToken = null;
		boolean matched = false;
		while (scanner.hasMoreChar()) {
			matched = false;
			for (IRule rule : rules) {
				// 从当前文字开始，测试每一个规则，看是否符合规则，如果符合，则生成新的分段，扫描器的当前位置也会向后移动
				// 否则规则恢复扫描器当前位置，由下一个扫描器测试，这里，每个规则都要保证扫描器的当前位置是正确的
				newToken = rule.evaluate(scanner);
				if (newToken != null) {
					// 在生成新的着色分段后，先判断之前默认的分段是否有长度
					if (defaultToken.getLength() > 0) {
						// 判断默认分段是否与修改前的分段相同（也就是在修改后，分段只是平移，没有被破坏）
						// 如果是这样，则认为从这个分段向后的所有分段都没有受到影响，可以直接平移使用，无需再扫描，提高效率
						if (isExists(newList, affectIndex, defaultToken, shift)) {
							return;
						}
						// 这是个新分段，需要着色
						paintToken(defaultToken);
						newList.add(defaultToken);
					}
					// 同上面的默认分段，这里处理的是着色分段
					if (isExists(newList, affectIndex, defaultToken, shift)) {
						return;
					}
					paintToken(newToken);
					newList.add(newToken);
					matched = true;
					break;
				}
			}
			// 找到一个新的着色分段，则默认分段被归零，重新计算
			if (matched) {
				defaultToken = new Token(scanner.getOffset(), 0, DEFAULT);
			} else {
				// 所有的规则都不符合，默认分段增加此字符
				defaultToken.increaseLength();
				// 扫描器跳一个字符
				scanner.read();
			}
		}
		// 最后还剩余默认分段，也要加入分段列表
		// defaultToken.increaseLength();
		if (defaultToken.getLength() > 0) {
			paintToken(defaultToken);
			newList.add(defaultToken);
		}
		// 保存新生成的着色分段列表
		tokenList = newList;
	}

	/**
	 * 判断上次分段列表中是否存在一个分段，在修改了指定偏移量后与新的分段相同， 如果相同，则认为后续的分段都可以沿用，无需修改
	 * 使用了实例变量tokenList：上次分段列表
	 * 
	 * @param newList
	 *            新的分段列表，用于追加后续分段
	 * @param fromIndex
	 *            旧分段开始位置
	 * @param token
	 *            新的分段
	 * @param shift
	 *            偏移量
	 * @return
	 */
	private boolean isExists(List<Token> newList, int fromIndex, Token token,
			int shift) {

		for (int index = fromIndex; index < tokenList.size(); ++index) {
			// 相同指：偏移量，长度，样式都一样
			if (token.equalsWithShift(tokenList.get(index), shift)) {
				for (int i = index; i < tokenList.size(); ++i) {
					if (tokenList.get(i).shift(shift)) {
						newList.add(tokenList.get(i));
					}
				}
				tokenList = newList;
				return true;
			}
		}
		return false;
	}

	private void paintToken(Token token) {
		// 偏移量，长度，样式全都具备，只需要轻轻的一画，着色就完成了
		int start = token.getOffset();
		int length = token.getLength();
		setStyleRanges(start, length, new int[] { start, length },
				new StyleRange[] { styles.get(token.getName()) });
	}

	/**
	 * 增加着色规则，规则中也包括着色样式，一同增加到编辑器中
	 * 
	 * @param paramRules
	 *            所有的着色规则数组，按优先级排序
	 */
	public void addRule(IRule... paramRules) {
		for (IRule rule : paramRules) {
			styles.put(rule.getName(), rule.getStyle());
			rule.getStyle().data = rule.getName();
			this.rules.add(rule);
		}
	}

	/**
	 * 设置为true 表示在 设置 false 之前所有的文本改都不添加至历史记录
	 * */
	public void setUndoLock(boolean undoLock) {
		this.undoLock = undoLock;
	}

	/**
	 * 能过规则获取文本中已应用的文本
	 * 
	 * @return 应用该规则的文本样式
	 * */
	public List<StyleRange> getStyleRangeByRule(String ruleName) {
		List<StyleRange> ranges = new ArrayList<StyleRange>();
		for (StyleRange r : getStyleRanges()) {
			if (ruleName.equals(r.data)) {
				ranges.add(r);
			}
		}
		return ranges;
	}

	@Override
	protected void checkSubclass() {
		// super.checkSubclass();
	}

	public static void main(String[] args) {
		System.out.println(SWT.MOD1 + 'Z');
	}
}
